{
 "nbformat": 4,
 "nbformat_minor": 0,
 "metadata": {
  "colab": {
   "provenance": []
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "J-hD3iZvxGwg",
    "outputId": "e5e9e7eb-1928-4b2d-cbf2-36b5e268bea5"
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "Collecting langgraph\n",
      "  Downloading langgraph-0.4.8-py3-none-any.whl.metadata (6.8 kB)\n",
      "Requirement already satisfied: langchain in /usr/local/lib/python3.11/dist-packages (0.3.25)\n",
      "Collecting litellm\n",
      "  Downloading litellm-1.72.2-py3-none-any.whl.metadata (39 kB)\n",
      "Collecting langchain-community\n",
      "  Downloading langchain_community-0.3.24-py3-none-any.whl.metadata (2.5 kB)\n",
      "Requirement already satisfied: geopy in /usr/local/lib/python3.11/dist-packages (2.4.1)\n",
      "Requirement already satisfied: requests in /usr/local/lib/python3.11/dist-packages (2.32.3)\n",
      "Requirement already satisfied: langchain-core>=0.1 in /usr/local/lib/python3.11/dist-packages (from langgraph) (0.3.63)\n",
      "Collecting langgraph-checkpoint>=2.0.26 (from langgraph)\n",
      "  Downloading langgraph_checkpoint-2.0.26-py3-none-any.whl.metadata (4.6 kB)\n",
      "Collecting langgraph-prebuilt>=0.2.0 (from langgraph)\n",
      "  Downloading langgraph_prebuilt-0.2.2-py3-none-any.whl.metadata (4.5 kB)\n",
      "Collecting langgraph-sdk>=0.1.42 (from langgraph)\n",
      "  Downloading langgraph_sdk-0.1.70-py3-none-any.whl.metadata (1.5 kB)\n",
      "Requirement already satisfied: pydantic>=2.7.4 in /usr/local/lib/python3.11/dist-packages (from langgraph) (2.11.5)\n",
      "Requirement already satisfied: xxhash>=3.5.0 in /usr/local/lib/python3.11/dist-packages (from langgraph) (3.5.0)\n",
      "Requirement already satisfied: langchain-text-splitters<1.0.0,>=0.3.8 in /usr/local/lib/python3.11/dist-packages (from langchain) (0.3.8)\n",
      "Requirement already satisfied: langsmith<0.4,>=0.1.17 in /usr/local/lib/python3.11/dist-packages (from langchain) (0.3.44)\n",
      "Requirement already satisfied: SQLAlchemy<3,>=1.4 in /usr/local/lib/python3.11/dist-packages (from langchain) (2.0.41)\n",
      "Requirement already satisfied: PyYAML>=5.3 in /usr/local/lib/python3.11/dist-packages (from langchain) (6.0.2)\n",
      "Requirement already satisfied: aiohttp in /usr/local/lib/python3.11/dist-packages (from litellm) (3.11.15)\n",
      "Requirement already satisfied: click in /usr/local/lib/python3.11/dist-packages (from litellm) (8.2.1)\n",
      "Requirement already satisfied: httpx>=0.23.0 in /usr/local/lib/python3.11/dist-packages (from litellm) (0.28.1)\n",
      "Requirement already satisfied: importlib-metadata>=6.8.0 in /usr/local/lib/python3.11/dist-packages (from litellm) (8.7.0)\n",
      "Requirement already satisfied: jinja2<4.0.0,>=3.1.2 in /usr/local/lib/python3.11/dist-packages (from litellm) (3.1.6)\n",
      "Requirement already satisfied: jsonschema<5.0.0,>=4.22.0 in /usr/local/lib/python3.11/dist-packages (from litellm) (4.24.0)\n",
      "Requirement already satisfied: openai>=1.68.2 in /usr/local/lib/python3.11/dist-packages (from litellm) (1.84.0)\n",
      "Collecting python-dotenv>=0.2.0 (from litellm)\n",
      "  Downloading python_dotenv-1.1.0-py3-none-any.whl.metadata (24 kB)\n",
      "Requirement already satisfied: tiktoken>=0.7.0 in /usr/local/lib/python3.11/dist-packages (from litellm) (0.9.0)\n",
      "Requirement already satisfied: tokenizers in /usr/local/lib/python3.11/dist-packages (from litellm) (0.21.1)\n",
      "Requirement already satisfied: tenacity!=8.4.0,<10,>=8.1.0 in /usr/local/lib/python3.11/dist-packages (from langchain-community) (9.1.2)\n",
      "Collecting dataclasses-json<0.7,>=0.5.7 (from langchain-community)\n",
      "  Downloading dataclasses_json-0.6.7-py3-none-any.whl.metadata (25 kB)\n",
      "Collecting pydantic-settings<3.0.0,>=2.4.0 (from langchain-community)\n",
      "  Downloading pydantic_settings-2.9.1-py3-none-any.whl.metadata (3.8 kB)\n",
      "Collecting httpx-sse<1.0.0,>=0.4.0 (from langchain-community)\n",
      "  Downloading httpx_sse-0.4.0-py3-none-any.whl.metadata (9.0 kB)\n",
      "Requirement already satisfied: numpy>=1.26.2 in /usr/local/lib/python3.11/dist-packages (from langchain-community) (2.0.2)\n",
      "Requirement already satisfied: geographiclib<3,>=1.52 in /usr/local/lib/python3.11/dist-packages (from geopy) (2.0)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.11/dist-packages (from requests) (3.4.2)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.11/dist-packages (from requests) (3.10)\n",
      "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.11/dist-packages (from requests) (2.4.0)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.11/dist-packages (from requests) (2025.4.26)\n",
      "Requirement already satisfied: aiohappyeyeballs>=2.3.0 in /usr/local/lib/python3.11/dist-packages (from aiohttp->litellm) (2.6.1)\n",
      "Requirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.11/dist-packages (from aiohttp->litellm) (1.3.2)\n",
      "Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.11/dist-packages (from aiohttp->litellm) (25.3.0)\n",
      "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.11/dist-packages (from aiohttp->litellm) (1.6.0)\n",
      "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.11/dist-packages (from aiohttp->litellm) (6.4.4)\n",
      "Requirement already satisfied: propcache>=0.2.0 in /usr/local/lib/python3.11/dist-packages (from aiohttp->litellm) (0.3.1)\n",
      "Requirement already satisfied: yarl<2.0,>=1.17.0 in /usr/local/lib/python3.11/dist-packages (from aiohttp->litellm) (1.20.0)\n",
      "Collecting marshmallow<4.0.0,>=3.18.0 (from dataclasses-json<0.7,>=0.5.7->langchain-community)\n",
      "  Downloading marshmallow-3.26.1-py3-none-any.whl.metadata (7.3 kB)\n",
      "Collecting typing-inspect<1,>=0.4.0 (from dataclasses-json<0.7,>=0.5.7->langchain-community)\n",
      "  Downloading typing_inspect-0.9.0-py3-none-any.whl.metadata (1.5 kB)\n",
      "Requirement already satisfied: anyio in /usr/local/lib/python3.11/dist-packages (from httpx>=0.23.0->litellm) (4.9.0)\n",
      "Requirement already satisfied: httpcore==1.* in /usr/local/lib/python3.11/dist-packages (from httpx>=0.23.0->litellm) (1.0.9)\n",
      "Requirement already satisfied: h11>=0.16 in /usr/local/lib/python3.11/dist-packages (from httpcore==1.*->httpx>=0.23.0->litellm) (0.16.0)\n",
      "Requirement already satisfied: zipp>=3.20 in /usr/local/lib/python3.11/dist-packages (from importlib-metadata>=6.8.0->litellm) (3.22.0)\n",
      "Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.11/dist-packages (from jinja2<4.0.0,>=3.1.2->litellm) (3.0.2)\n",
      "Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /usr/local/lib/python3.11/dist-packages (from jsonschema<5.0.0,>=4.22.0->litellm) (2025.4.1)\n",
      "Requirement already satisfied: referencing>=0.28.4 in /usr/local/lib/python3.11/dist-packages (from jsonschema<5.0.0,>=4.22.0->litellm) (0.36.2)\n",
      "Requirement already satisfied: rpds-py>=0.7.1 in /usr/local/lib/python3.11/dist-packages (from jsonschema<5.0.0,>=4.22.0->litellm) (0.25.1)\n",
      "Requirement already satisfied: jsonpatch<2.0,>=1.33 in /usr/local/lib/python3.11/dist-packages (from langchain-core>=0.1->langgraph) (1.33)\n",
      "Requirement already satisfied: packaging<25,>=23.2 in /usr/local/lib/python3.11/dist-packages (from langchain-core>=0.1->langgraph) (24.2)\n",
      "Requirement already satisfied: typing-extensions>=4.7 in /usr/local/lib/python3.11/dist-packages (from langchain-core>=0.1->langgraph) (4.14.0)\n",
      "Collecting ormsgpack<2.0.0,>=1.8.0 (from langgraph-checkpoint>=2.0.26->langgraph)\n",
      "  Downloading ormsgpack-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (43 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m43.7/43.7 kB\u001b[0m \u001b[31m2.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: orjson>=3.10.1 in /usr/local/lib/python3.11/dist-packages (from langgraph-sdk>=0.1.42->langgraph) (3.10.18)\n",
      "Requirement already satisfied: requests-toolbelt<2.0.0,>=1.0.0 in /usr/local/lib/python3.11/dist-packages (from langsmith<0.4,>=0.1.17->langchain) (1.0.0)\n",
      "Requirement already satisfied: zstandard<0.24.0,>=0.23.0 in /usr/local/lib/python3.11/dist-packages (from langsmith<0.4,>=0.1.17->langchain) (0.23.0)\n",
      "Requirement already satisfied: distro<2,>=1.7.0 in /usr/local/lib/python3.11/dist-packages (from openai>=1.68.2->litellm) (1.9.0)\n",
      "Requirement already satisfied: jiter<1,>=0.4.0 in /usr/local/lib/python3.11/dist-packages (from openai>=1.68.2->litellm) (0.10.0)\n",
      "Requirement already satisfied: sniffio in /usr/local/lib/python3.11/dist-packages (from openai>=1.68.2->litellm) (1.3.1)\n",
      "Requirement already satisfied: tqdm>4 in /usr/local/lib/python3.11/dist-packages (from openai>=1.68.2->litellm) (4.67.1)\n",
      "Requirement already satisfied: annotated-types>=0.6.0 in /usr/local/lib/python3.11/dist-packages (from pydantic>=2.7.4->langgraph) (0.7.0)\n",
      "Requirement already satisfied: pydantic-core==2.33.2 in /usr/local/lib/python3.11/dist-packages (from pydantic>=2.7.4->langgraph) (2.33.2)\n",
      "Requirement already satisfied: typing-inspection>=0.4.0 in /usr/local/lib/python3.11/dist-packages (from pydantic>=2.7.4->langgraph) (0.4.1)\n",
      "Requirement already satisfied: greenlet>=1 in /usr/local/lib/python3.11/dist-packages (from SQLAlchemy<3,>=1.4->langchain) (3.2.2)\n",
      "Requirement already satisfied: regex>=2022.1.18 in /usr/local/lib/python3.11/dist-packages (from tiktoken>=0.7.0->litellm) (2024.11.6)\n",
      "Requirement already satisfied: huggingface-hub<1.0,>=0.16.4 in /usr/local/lib/python3.11/dist-packages (from tokenizers->litellm) (0.32.4)\n",
      "Requirement already satisfied: filelock in /usr/local/lib/python3.11/dist-packages (from huggingface-hub<1.0,>=0.16.4->tokenizers->litellm) (3.18.0)\n",
      "Requirement already satisfied: fsspec>=2023.5.0 in /usr/local/lib/python3.11/dist-packages (from huggingface-hub<1.0,>=0.16.4->tokenizers->litellm) (2025.3.2)\n",
      "Requirement already satisfied: hf-xet<2.0.0,>=1.1.2 in /usr/local/lib/python3.11/dist-packages (from huggingface-hub<1.0,>=0.16.4->tokenizers->litellm) (1.1.2)\n",
      "Requirement already satisfied: jsonpointer>=1.9 in /usr/local/lib/python3.11/dist-packages (from jsonpatch<2.0,>=1.33->langchain-core>=0.1->langgraph) (3.0.0)\n",
      "Collecting mypy-extensions>=0.3.0 (from typing-inspect<1,>=0.4.0->dataclasses-json<0.7,>=0.5.7->langchain-community)\n",
      "  Downloading mypy_extensions-1.1.0-py3-none-any.whl.metadata (1.1 kB)\n",
      "Downloading langgraph-0.4.8-py3-none-any.whl (152 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m152.4/152.4 kB\u001b[0m \u001b[31m6.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading litellm-1.72.2-py3-none-any.whl (8.0 MB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m8.0/8.0 MB\u001b[0m \u001b[31m49.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading langchain_community-0.3.24-py3-none-any.whl (2.5 MB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2.5/2.5 MB\u001b[0m \u001b[31m44.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading dataclasses_json-0.6.7-py3-none-any.whl (28 kB)\n",
      "Downloading httpx_sse-0.4.0-py3-none-any.whl (7.8 kB)\n",
      "Downloading langgraph_checkpoint-2.0.26-py3-none-any.whl (44 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m44.2/44.2 kB\u001b[0m \u001b[31m2.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading langgraph_prebuilt-0.2.2-py3-none-any.whl (23 kB)\n",
      "Downloading langgraph_sdk-0.1.70-py3-none-any.whl (49 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m50.0/50.0 kB\u001b[0m \u001b[31m2.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading pydantic_settings-2.9.1-py3-none-any.whl (44 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m44.4/44.4 kB\u001b[0m \u001b[31m2.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading python_dotenv-1.1.0-py3-none-any.whl (20 kB)\n",
      "Downloading marshmallow-3.26.1-py3-none-any.whl (50 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m50.9/50.9 kB\u001b[0m \u001b[31m2.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading ormsgpack-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (216 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m216.5/216.5 kB\u001b[0m \u001b[31m2.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading typing_inspect-0.9.0-py3-none-any.whl (8.8 kB)\n",
      "Downloading mypy_extensions-1.1.0-py3-none-any.whl (5.0 kB)\n",
      "Installing collected packages: python-dotenv, ormsgpack, mypy-extensions, marshmallow, httpx-sse, typing-inspect, pydantic-settings, langgraph-sdk, dataclasses-json, litellm, langgraph-checkpoint, langgraph-prebuilt, langgraph, langchain-community\n",
      "Successfully installed dataclasses-json-0.6.7 httpx-sse-0.4.0 langchain-community-0.3.24 langgraph-0.4.8 langgraph-checkpoint-2.0.26 langgraph-prebuilt-0.2.2 langgraph-sdk-0.1.70 litellm-1.72.2 marshmallow-3.26.1 mypy-extensions-1.1.0 ormsgpack-1.10.0 pydantic-settings-2.9.1 python-dotenv-1.1.0 typing-inspect-0.9.0\n"
     ]
    }
   ],
   "source": [
    "!pip install langgraph langchain litellm langchain-community geopy requests"
   ]
  },
  {
   "cell_type": "markdown",
   "source": "ReAct agent from scratch with Nebius Token Factory and LangGraph\n\nLangGraph is a framework for building stateful LLM applications, making it a good choice for constructing ReAct (Reasoning and Acting) Agents.\n\nReAct agents combine LLM reasoning with action execution. They iteratively think, use tools, and act on observations to achieve user goals, dynamically adapting their approach.\n\nWhile LangGraph offers a prebuilt ReAct agent (create_react_agent), it shines when you need more control and customization for your ReAct implementations.\n\nLangGraph models agents as graphs using three key components:\n\nState: Shared data structure (typically TypedDict or Pydantic BaseModel) representing the application's current snapshot.\nNodes: Encodes logic of your agents. They receive the current State as input, perform some computation or side-effect, and return an updated State, such as LLM calls or tool calls.\nEdges: Define the next Node to execute based on the current State, allowing for conditional logic and fixed transitions.",
   "metadata": {
    "id": "-js_vFaUxd9R"
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "Set your API key in the environment variable `NEBIUS_API_KEY`"
   ],
   "metadata": {
    "id": "2ZrQn3lDx1Lg"
   }
  },
  {
   "cell_type": "code",
   "source": [
    "import os\n",
    "\n",
    "# Read your API key from the environment variable or set it manually\n",
    "api_key = os.getenv(\"NEBIUS_API_KEY\")"
   ],
   "metadata": {
    "id": "QidsDZ63xqBl"
   },
   "execution_count": 3,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "source": [
    "To better understand how to implement a ReAct agent using LangGraph, let's walk through a practical example. You will create a simple agent whose goal is to use a tool to find the current weather for a specified location.\n",
    "\n",
    "For this weather agent, its State will need to maintain the ongoing conversation history (as a list of messages) and a counter for the number of steps taken to further illustrate state management.\n",
    "\n",
    "LangGraph provides a convenient helper, add_messages, for updating message lists in the state. It functions as a reducer, meaning it takes the current list and new messages, then returns a combined list. It smartly handles updates by message ID and defaults to an \"append-only\" behavior for new, unique messages."
   ],
   "metadata": {
    "id": "XPmkVjrfzHNO"
   }
  },
  {
   "cell_type": "code",
   "source": [
    "from typing import Annotated,Sequence, TypedDict\n",
    "\n",
    "from langchain_core.messages import BaseMessage\n",
    "from langgraph.graph.message import add_messages # helper function to add messages to the state\n",
    "\n",
    "\n",
    "class AgentState(TypedDict):\n",
    "    \"\"\"The state of the agent.\"\"\"\n",
    "    messages: Annotated[Sequence[BaseMessage], add_messages]\n",
    "    number_of_steps: int"
   ],
   "metadata": {
    "id": "WBG3w7hfyEd7"
   },
   "execution_count": 4,
   "outputs": []
  },
  {
   "cell_type": "code",
   "source": [
    "from langchain_core.tools import tool\n",
    "from geopy.geocoders import Nominatim\n",
    "from pydantic import BaseModel, Field\n",
    "import requests\n",
    "\n",
    "geolocator = Nominatim(user_agent=\"weather-app\")\n",
    "\n",
    "class SearchInput(BaseModel):\n",
    "    location:str = Field(description=\"The city and state, e.g., San Francisco\")\n",
    "    date:str = Field(description=\"the forecasting date for when to get the weather format (yyyy-mm-dd)\")\n",
    "\n",
    "@tool(\"get_weather_forecast\", args_schema=SearchInput, return_direct=True)\n",
    "def get_weather_forecast(location: str, date: str):\n",
    "    \"\"\"Retrieves the weather using Open-Meteo API for a given location (city) and a date (yyyy-mm-dd). Returns a list dictionary with the time and temperature for each hour.\"\"\"\n",
    "    location = geolocator.geocode(location)\n",
    "    if location:\n",
    "        try:\n",
    "            response = requests.get(f\"https://api.open-meteo.com/v1/forecast?latitude={location.latitude}&longitude={location.longitude}&hourly=temperature_2m&start_date={date}&end_date={date}\")\n",
    "            data = response.json()\n",
    "            return {time: temp for time, temp in zip(data[\"hourly\"][\"time\"], data[\"hourly\"][\"temperature_2m\"])}\n",
    "        except Exception as e:\n",
    "            return {\"error\": str(e)}\n",
    "    else:\n",
    "        return {\"error\": \"Location not found\"}\n",
    "\n",
    "tools = [get_weather_forecast]"
   ],
   "metadata": {
    "id": "T1lZQs6TyElz"
   },
   "execution_count": 5,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "source": [
    "Next, you initialize your model and bind the tools to the model.\n",
    "\n"
   ],
   "metadata": {
    "id": "C2kYnD56zKpV"
   }
  },
  {
   "cell_type": "code",
   "source": [
    "from datetime import datetime\n",
    "from langchain_community.chat_models import ChatLiteLLM\n",
    "\n",
    "\n",
    "# Create LLM class\n",
    "llm = ChatLiteLLM(model=\"nebius/Qwen/Qwen3-235B-A22B\")\n",
    "\n",
    "\n",
    "# Bind tools to the model\n",
    "model = llm.bind_tools([get_weather_forecast])\n",
    "\n",
    "# Test the model with tools\n",
    "res=model.invoke(f\"What is the weather in Berlin on {datetime.today()}?\")\n",
    "\n",
    "print(res)"
   ],
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "NBzl97DtyEp1",
    "outputId": "f85e16e0-5dde-4b0c-ea8d-c1bc6bee6b83"
   },
   "execution_count": 6,
   "outputs": [
    {
     "output_type": "stream",
     "name": "stderr",
     "text": [
      "<ipython-input-6-b22f41d41f7c>:6: LangChainDeprecationWarning: The class `ChatLiteLLM` was deprecated in LangChain 0.3.24 and will be removed in 1.0. An updated version of the class exists in the :class:`~langchain-litellm package and should be used instead. To use it run `pip install -U :class:`~langchain-litellm` and import as `from :class:`~langchain_litellm import ChatLiteLLM``.\n",
      "  llm = ChatLiteLLM(model=\"nebius/Qwen/Qwen3-235B-A22B\")\n"
     ]
    },
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "content='<think>\\nOkay, the user is asking for the weather in Berlin on June 9, 2025, at 17:44. Let me check the tools available. There\\'s a function called get_weather_forecast that takes location and date parameters. The date needs to be in yyyy-mm-dd format. The user\\'s query includes the date and time, but the function\\'s date parameter only requires the date part, not the full timestamp. So I should extract just the date portion from the user\\'s input. The location is Berlin. Wait, the example for location was San Francisco, but Berlin is a city too. The function should accept Berlin as the location. I\\'ll need to call get_weather_forecast with location=\"Berlin\" and date=\"2025-06-09\". I don\\'t need the time part from the user\\'s query since the function\\'s date parameter is a date only. That should work. Let me structure the JSON accordingly.\\n</think>\\n\\n' additional_kwargs={'tool_calls': [ChatCompletionMessageToolCall(function=Function(arguments='{\"location\": \"Berlin\", \"date\": \"2025-06-09\"}', name='get_weather_forecast'), id='chatcmpl-tool-47df0e6438964e65a051eea147a1b4d1', type='function')]} response_metadata={'token_usage': Usage(completion_tokens=239, prompt_tokens=261, total_tokens=500, completion_tokens_details=None, prompt_tokens_details=None), 'model': 'nebius/Qwen/Qwen3-235B-A22B', 'finish_reason': 'tool_calls', 'model_name': 'nebius/Qwen/Qwen3-235B-A22B'} id='run--deb01ca4-cbac-424b-b5fb-793b7f47d14e-0' tool_calls=[{'name': 'get_weather_forecast', 'args': {'location': 'Berlin', 'date': '2025-06-09'}, 'id': 'chatcmpl-tool-47df0e6438964e65a051eea147a1b4d1', 'type': 'tool_call'}] usage_metadata={'input_tokens': 261, 'output_tokens': 239, 'total_tokens': 500}\n"
     ]
    }
   ]
  },
  {
   "cell_type": "markdown",
   "source": [
    "The last step before you can run your agent is to define your nodes and edges. In this example, you have two nodes and one edge. - call_tool node that executes your tool method. LangGraph has a prebuilt node for this called ToolNode. - call_model node that uses the model_with_tools to call the model. - should_continue edge that decides whether to call the tool or the model.\n",
    "\n",
    "The number of nodes and edges is not fixed. You can add as many nodes and edges as you want to your graph. For example, you could add a node for adding structured output or a self-verification/reflection node to check the model output before calling the tool or the model."
   ],
   "metadata": {
    "id": "iiT-Qv37zOVj"
   }
  },
  {
   "cell_type": "code",
   "source": [
    "from langchain_core.messages import ToolMessage\n",
    "from langchain_core.runnables import RunnableConfig\n",
    "\n",
    "tools_by_name = {tool.name: tool for tool in tools}\n",
    "\n",
    "# Define our tool node\n",
    "def call_tool(state: AgentState):\n",
    "    outputs = []\n",
    "    # Iterate over the tool calls in the last message\n",
    "    for tool_call in state[\"messages\"][-1].tool_calls:\n",
    "        # Get the tool by name\n",
    "        tool_result = tools_by_name[tool_call[\"name\"]].invoke(tool_call[\"args\"])\n",
    "        outputs.append(\n",
    "            ToolMessage(\n",
    "                content=tool_result,\n",
    "                name=tool_call[\"name\"],\n",
    "                tool_call_id=tool_call[\"id\"],\n",
    "            )\n",
    "        )\n",
    "    return {\"messages\": outputs}\n",
    "\n",
    "def call_model(\n",
    "    state: AgentState,\n",
    "    config: RunnableConfig,\n",
    "):\n",
    "    # Invoke the model with the system prompt and the messages\n",
    "    response = model.invoke(state[\"messages\"], config)\n",
    "    # We return a list, because this will get added to the existing messages state using the add_messages reducer\n",
    "    return {\"messages\": [response]}\n",
    "\n",
    "\n",
    "# Define the conditional edge that determines whether to continue or not\n",
    "def should_continue(state: AgentState):\n",
    "    messages = state[\"messages\"]\n",
    "    # If the last message is not a tool call, then we finish\n",
    "    if not messages[-1].tool_calls:\n",
    "        return \"end\"\n",
    "    # default to continue\n",
    "    return \"continue\""
   ],
   "metadata": {
    "id": "9aMKFZ9gyEwj"
   },
   "execution_count": 7,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "source": [
    "Now you have all the components to build your agent. Let's put them together.\n",
    "\n"
   ],
   "metadata": {
    "id": "52lGz2V2zR5-"
   }
  },
  {
   "cell_type": "code",
   "source": [
    "from langgraph.graph import StateGraph, END\n",
    "\n",
    "# Define a new graph with our state\n",
    "workflow = StateGraph(AgentState)\n",
    "\n",
    "# 1. Add our nodes\n",
    "workflow.add_node(\"llm\", call_model)\n",
    "workflow.add_node(\"tools\",  call_tool)\n",
    "# 2. Set the entrypoint as `agent`, this is the first node called\n",
    "workflow.set_entry_point(\"llm\")\n",
    "# 3. Add a conditional edge after the `llm` node is called.\n",
    "workflow.add_conditional_edges(\n",
    "    # Edge is used after the `llm` node is called.\n",
    "    \"llm\",\n",
    "    # The function that will determine which node is called next.\n",
    "    should_continue,\n",
    "    # Mapping for where to go next, keys are strings from the function return, and the values are other nodes.\n",
    "    # END is a special node marking that the graph is finish.\n",
    "    {\n",
    "        # If `tools`, then we call the tool node.\n",
    "        \"continue\": \"tools\",\n",
    "        # Otherwise we finish.\n",
    "        \"end\": END,\n",
    "    },\n",
    ")\n",
    "# 4. Add a normal edge after `tools` is called, `llm` node is called next.\n",
    "workflow.add_edge(\"tools\", \"llm\")\n",
    "\n",
    "# Now we can compile and visualize our graph\n",
    "graph = workflow.compile()"
   ],
   "metadata": {
    "id": "BEOHTE3ZyEzv"
   },
   "execution_count": 8,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "source": [
    "You can visualize your graph using the draw_mermaid_png method.\n",
    "\n"
   ],
   "metadata": {
    "id": "BXhBXjjHzUUP"
   }
  },
  {
   "cell_type": "code",
   "source": [
    "from IPython.display import Image, display\n",
    "\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ],
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 290
    },
    "id": "XGRFaNP4yOPU",
    "outputId": "6f331efc-a9cb-4a5b-b916-3170733846c3"
   },
   "execution_count": 9,
   "outputs": [
    {
     "output_type": "display_data",
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {}
    }
   ]
  },
  {
   "cell_type": "code",
   "source": [
    "from datetime import datetime\n",
    "# Create our initial message dictionary\n",
    "inputs = {\"messages\": [(\"user\", f\"What is the weather in Berlin on {datetime.today()}?\")]}\n",
    "\n",
    "# call our graph with streaming to see the steps\n",
    "for state in graph.stream(inputs, stream_mode=\"values\"):\n",
    "    last_message = state[\"messages\"][-1]\n",
    "    last_message.pretty_print()"
   ],
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "pUbIeA2RyOV5",
    "outputId": "a64310f1-5671-4e8d-89b4-805ee991bc68"
   },
   "execution_count": 10,
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "What is the weather in Berlin on 2025-06-09 17:44:41.953543?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "<think>\n",
      "Okay, the user is asking for the weather in Berlin on June 9, 2025, at 17:44:41.953543. Let me check the tools available. There's a function called get_weather_forecast that takes location and date parameters. The date needs to be in yyyy-mm-dd format. The user provided the date as 2025-06-09, which is already in the correct format. The time part here is 17:44:41, but the function's description says it returns hourly temperature. So maybe the time in the query is not necessary for the function call because the function might aggregate by hour. Wait, the parameters require location and date, so I should extract Berlin and the date 2025-06-09. The timestamp after the date isn't needed since the function's date parameter is just the day. So I need to call get_weather_forecast with location \"Berlin\" and date \"2025-06-09\". That's the correct approach.\n",
      "</think>\n",
      "Tool Calls:\n",
      "  get_weather_forecast (chatcmpl-tool-1d8e7cca7630429a9bd16feab7d342c9)\n",
      " Call ID: chatcmpl-tool-1d8e7cca7630429a9bd16feab7d342c9\n",
      "  Args:\n",
      "    location: Berlin\n",
      "    date: 2025-06-09\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: get_weather_forecast\n",
      "\n",
      "{'2025-06-09T00:00': 12.0, '2025-06-09T01:00': 11.8, '2025-06-09T02:00': 11.7, '2025-06-09T03:00': 11.7, '2025-06-09T04:00': 11.7, '2025-06-09T05:00': 11.5, '2025-06-09T06:00': 12.2, '2025-06-09T07:00': 12.8, '2025-06-09T08:00': 13.5, '2025-06-09T09:00': 13.6, '2025-06-09T10:00': 14.2, '2025-06-09T11:00': 15.5, '2025-06-09T12:00': 16.4, '2025-06-09T13:00': 16.9, '2025-06-09T14:00': 17.4, '2025-06-09T15:00': 18.6, '2025-06-09T16:00': 18.6, '2025-06-09T17:00': 18.8, '2025-06-09T18:00': 18.2, '2025-06-09T19:00': 17.6, '2025-06-09T20:00': 16.7, '2025-06-09T21:00': 15.9, '2025-06-09T22:00': 14.8, '2025-06-09T23:00': 14.0}\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "<think>\n",
      "Okay, the user asked for the weather in Berlin on June 9, 2025, at 17:44:41. The function call was made with the date 2025-06-09, and the response has hourly temperature data. The user's specific time is 17:44, which falls between 17:00 and 18:00.\n",
      "\n",
      "Looking at the data, the temperature at 17:00 is 18.8°C. Since the data is hourly, the temperature at 17:44 would be closest to the 17:00 value. I should mention that the temperature at 17:00 is 18.8°C and note that the data is hourly, so there's no exact value for 17:44. It's also good to provide the surrounding data points, like 18:00 being 18.2°C, to give context. The answer should be clear about the limitations of the data and offer the most accurate available info.\n",
      "</think>\n",
      "\n",
      "The weather in Berlin on **June 9, 2025** at **17:44:41** (UTC) is closest to the hourly recorded temperature at **17:00**, which is **18.8°C**. \n",
      "\n",
      "The temperature at **18:00** drops to **18.2°C**. Since the data is recorded hourly, the value at **17:44:41** would likely be transitioning between these two points. \n",
      "\n",
      "Let me know if you need further details! 🌤️\n"
     ]
    }
   ]
  },
  {
   "cell_type": "code",
   "source": [
    "state[\"messages\"].append((\"user\", \"Would it be in reykjavik warmer?\"))\n",
    "\n",
    "for state in graph.stream(state, stream_mode=\"values\"):\n",
    "    last_message = state[\"messages\"][-1]\n",
    "    last_message.pretty_print()"
   ],
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "x4_sX6Q2yQhR",
    "outputId": "c5efe33a-6414-4324-f518-14c984acce07"
   },
   "execution_count": 12,
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "Would it be in reykjavik warmer?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "<think>\n",
      "Okay, the user is asking if Reykjavik would be warmer on June 9, 2025, at the same time, 17:44:41. They previously asked about Berlin and Munich, so they're comparing temperatures across different cities.\n",
      "\n",
      "First, I need to check the weather forecast for Reykjavik on that date. The function get_weather_forecast requires location (city) and date. The date is still 2025-06-09.\n",
      "\n",
      "I should call the function with location as Reykjavik and date as 2025-06-09. Since the user is following up, they expect a similar response structure as before.\n",
      "\n",
      "I need to make sure the tool call is correctly formatted. The function name is get_weather_forecast, arguments: location \"Reykjavik\" and date \"2025-06-09\".\n",
      "</think>\n",
      "Tool Calls:\n",
      "  get_weather_forecast (chatcmpl-tool-d93f12e69b13449c8b3994dda78c4dd6)\n",
      " Call ID: chatcmpl-tool-d93f12e69b13449c8b3994dda78c4dd6\n",
      "  Args:\n",
      "    location: Reykjavik\n",
      "    date: 2025-06-09\n"
     ]
    },
    {
     "output_type": "stream",
     "name": "stderr",
     "text": [
      "WARNING:urllib3.connectionpool:Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ReadTimeoutError(\"HTTPSConnectionPool(host='nominatim.openstreetmap.org', port=443): Read timed out. (read timeout=1)\")': /search?q=Reykjavik&format=json&limit=1\n"
     ]
    },
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: get_weather_forecast\n",
      "\n",
      "{'2025-06-09T00:00': 7.1, '2025-06-09T01:00': 6.6, '2025-06-09T02:00': 6.3, '2025-06-09T03:00': 5.9, '2025-06-09T04:00': 5.5, '2025-06-09T05:00': 5.6, '2025-06-09T06:00': 5.9, '2025-06-09T07:00': 6.4, '2025-06-09T08:00': 7.1, '2025-06-09T09:00': 8.1, '2025-06-09T10:00': 8.7, '2025-06-09T11:00': 9.4, '2025-06-09T12:00': 9.8, '2025-06-09T13:00': 10.4, '2025-06-09T14:00': 10.2, '2025-06-09T15:00': 10.4, '2025-06-09T16:00': 10.5, '2025-06-09T17:00': 10.5, '2025-06-09T18:00': 10.1, '2025-06-09T19:00': 9.9, '2025-06-09T20:00': 9.6, '2025-06-09T21:00': 9.4, '2025-06-09T22:00': 9.1, '2025-06-09T23:00': 9.0}\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "<think>\n",
      "Okay, the user asked if Reykjavik would be warmer on June 9, 2025, at 17:44:41. From previous interactions, they compared Berlin and Munich, so they're likely checking temperatures in different cities.\n",
      "\n",
      "First, I called the get_weather_forecast function for Reykjavik on that date. The response shows the temperature at 17:00 is 10.5°C. In earlier responses, Berlin was 18.8°C and Munich 19.6°C. \n",
      "\n",
      "Comparing these, Reykjavik is colder. The user might be planning a trip or event and checking which city is warmer. They might also be curious about seasonal differences or geographical factors affecting weather. \n",
      "\n",
      "I need to present the data clearly, mention the trend (Reykjavik peaks earlier at 10.5°C), and confirm that it's much cooler than Berlin and Munich. Offer further help in case they need more cities or details.\n",
      "</think>\n",
      "\n",
      "On **June 9, 2025**, at **17:00** (closest hourly data to 17:44:41), the temperature in **Reykjavik** is **10.5°C**. This is significantly cooler compared to:\n",
      "\n",
      "- **Berlin**: **18.8°C**  \n",
      "- **Munich**: **19.6°C**  \n",
      "\n",
      "Reykjavik’s temperature peaks earlier in the day at **10.5°C** (16:00–17:00) and gradually cools afterward, while Berlin and Munich show much higher midday-to-evening warmth. \n",
      "\n",
      "So, **Reykjavik would be much colder** than both cities at the specified date and time. Let me know if you'd like to check other locations! ❄️\n"
     ]
    }
   ]
  }
 ]
}